# app.py
import os
import json
import datetime
from flask import Flask, request, jsonify

app = Flask(__name__)
DATA_FILE = "tasks.json"
BASE_URL = "/api/v1"  # 与 OpenAPI servers.url 匹配

# --- Helper Functions ---


def load_tasks():
    """从 JSON 文件加载任务列表"""
    if not os.path.exists(DATA_FILE):
        return []
    try:
        with open(DATA_FILE, "r", encoding="utf-8") as f:
            tasks = json.load(f)
            # 确保 ID 是整数类型 (如果从其他来源导入可能不是)
            for task in tasks:
                if "id" in task and isinstance(task["id"], str):
                    try:
                        task["id"] = int(task["id"])
                    except ValueError:
                        # 处理无法转换的 ID，可以记录日志或跳过
                        print(
                            f"Warning: Could not convert task ID '{task['id']}' to int."
                        )
            return tasks
    except (json.JSONDecodeError, FileNotFoundError):
        return []  # 如果文件不存在或格式错误，返回空列表


def save_tasks(tasks):
    """将任务列表保存到 JSON 文件"""
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(tasks, f, indent=2, ensure_ascii=False)  # indent=2 格式化输出


def get_next_id(tasks):
    """生成下一个可用的任务 ID"""
    if not tasks:
        return 1
    # 确保所有 ID 都是整数再计算 max
    valid_ids = [task["id"] for task in tasks if isinstance(task.get("id"), int)]
    return max(valid_ids) + 1 if valid_ids else 1


def validate_task_input(data, is_update=False):
    """验证任务输入数据"""
    errors = {}
    required_fields = ["title"]
    allowed_status = ["todo", "in_progress", "done"]

    if not is_update:  # 创建时 title 必须存在
        if "title" not in data or not data["title"]:
            errors["title"] = "Title is required."
    elif "title" in data and not data["title"]:  # 更新时如果提供了 title，则不能为空
        errors["title"] = "Title cannot be empty if provided for update."

    if "status" in data and data["status"] not in allowed_status:
        errors["status"] = (
            f"Invalid status. Allowed values: {', '.join(allowed_status)}"
        )

    # 可选：验证 due_date 格式 (这里简化，不严格校验 ISO 8601)
    if "due_date" in data and data["due_date"] is not None:
        try:
            # 尝试解析，但不强制要求严格的 ISO 格式，可以根据需要加强
            datetime.datetime.fromisoformat(data["due_date"].replace("Z", "+00:00"))
        except (ValueError, TypeError):
            errors["due_date"] = "Invalid date-time format for due_date."

    return errors


# --- API Endpoints ---


@app.route(f"{BASE_URL}/tasks", methods=["GET"])
def list_tasks():
    """List all tasks (operationId: listTasks)"""
    tasks = load_tasks()
    return jsonify(tasks), 200


@app.route(f"{BASE_URL}/tasks", methods=["POST"])
def create_task():
    """Create a new task (operationId: createTask)"""
    if not request.is_json:
        return jsonify({"error": "Request must be JSON"}), 400

    data = request.get_json()
    validation_errors = validate_task_input(data)
    if validation_errors:
        return jsonify(
            {"error": "Invalid input data", "details": validation_errors}
        ), 400

    tasks = load_tasks()
    now = (
        datetime.datetime.now(datetime.timezone.utc)
        .isoformat(timespec="milliseconds")
        .replace("+00:00", "Z")
    )

    new_task = {
        "id": get_next_id(tasks),
        "title": data["title"],
        "description": data.get("description", ""),  # 使用 .get 提供默认值
        "status": data.get("status", "todo"),  # 默认 'todo'
        "due_date": data.get("due_date"),  # 可以是 None
        "created_at": now,
        "updated_at": now,
    }

    tasks.append(new_task)
    save_tasks(tasks)

    return jsonify(new_task), 201  # 201 Created


@app.route(f"{BASE_URL}/tasks/<int:task_id>", methods=["GET"])
def get_task_by_id(task_id):
    """Get a specific task by ID (operationId: getTaskById)"""
    tasks = load_tasks()
    task = next(
        (t for t in tasks if t.get("id") == task_id), None
    )  # 使用 .get 安全访问 id

    if task:
        return jsonify(task), 200
    else:
        return jsonify({"error": "Task not found"}), 404  # 404 Not Found


@app.route(f"{BASE_URL}/tasks/<int:task_id>", methods=["PUT"])
def update_task(task_id):
    """Update an existing task (operationId: updateTask)"""
    if not request.is_json:
        return jsonify({"error": "Request must be JSON"}), 400

    data = request.get_json()
    validation_errors = validate_task_input(data, is_update=True)
    if validation_errors:
        return jsonify(
            {"error": "Invalid input data", "details": validation_errors}
        ), 400

    tasks = load_tasks()
    task_index = -1
    for i, task in enumerate(tasks):
        if task.get("id") == task_id:
            task_index = i
            break

    if task_index == -1:
        return jsonify({"error": "Task not found"}), 404

    # 更新任务字段
    task_to_update = tasks[task_index]
    now = (
        datetime.datetime.now(datetime.timezone.utc)
        .isoformat(timespec="milliseconds")
        .replace("+00:00", "Z")
    )

    # 只更新请求中提供的字段
    if "title" in data:
        task_to_update["title"] = data["title"]
    if "description" in data:
        task_to_update["description"] = data["description"]
    if "status" in data:
        task_to_update["status"] = data["status"]
    if "due_date" in data:  # 允许将 due_date 设置为 null 或新日期
        task_to_update["due_date"] = data["due_date"]

    task_to_update["updated_at"] = now  # 始终更新 updated_at

    save_tasks(tasks)
    return jsonify(task_to_update), 200


@app.route(f"{BASE_URL}/tasks/<int:task_id>", methods=["DELETE"])
def delete_task(task_id):
    """Delete a task by ID (operationId: deleteTask)"""
    tasks = load_tasks()
    original_length = len(tasks)
    # 过滤掉要删除的任务
    tasks_after_deletion = [task for task in tasks if task.get("id") != task_id]

    if len(tasks_after_deletion) == original_length:
        # 如果长度没变，说明没找到该 ID 的任务
        return jsonify({"error": "Task not found"}), 404

    save_tasks(tasks_after_deletion)
    return "", 204  # 204 No Content


# --- Main Execution ---
if __name__ == "__main__":
    # 确保在启动时 tasks.json 文件存在（如果不存在则创建空列表文件）
    if not os.path.exists(DATA_FILE):
        save_tasks([])
    # 运行 Flask 开发服务器
    # host='0.0.0.0' 允许从网络中的其他机器访问
    # debug=True 会在代码更改时自动重载，并提供更详细的错误信息（不要在生产环境中使用）
    app.run(host="0.0.0.0", port=8080, debug=True)
